﻿import sys
import json
import os
import numpy as np
import pandas as pd
import requests

join = os.path.join
dirname = os.path.dirname

CURRENT_PATH = dirname(os.path.realpath(__file__))
sys.path.append(CURRENT_PATH)

pd.set_option('mode.chained_assignment', None)
from utils import cal_dist,\
    gaode_2_points_pre,sparse_points,pred_unit_mi,time_log

'''
该结果后续接入前端DEMO展示程序

具体逻辑：
1. 高德返回的路径点过多，使用DP算法做数据抽稀
2. 统计路径点的最大经度、最小经度、最大纬度、最小纬度，以这4个值再分别外移10KM，生成一个范围框
3. 圈出框内所有电桩，在此基础上筛选续驶里程可达到的电桩
4. 根据不同的筛选条件做进一步筛选并按照接口文档处理成展示用数据

input: json 高德路径规划结果，当次路径规划结果
inout: json 里程预测模型结果，剩余电量可跑的最远距离
output: pkl 推荐电桩结果
'''

@time_log('电桩推荐任务耗时')
def refer_ele_sta(input_1, input_2):
    
    # para
    unit_soc_mileage_min = 1.5 * 1000
    remain_soc = 2
    gongkuang_ratios = [0.5, 0.7] # 工况系数
    # 稀疏化点
    station_info = pd.read_csv(r"", low_memory=False, encoding="utf-8-sig")

    dist_limit = 10 * 1000
    station_ratio = 2
    dist_limit_of_fee_red = 5*1000
    rn = json.loads(input_2)

    # 预测里程和判断 根据 unit_soc_mileage_min 来判断
    distance = float(rn['route']['paths'][0]['distance'])
    clw = json.loads(input_1)
    start_soc = clw['start_soc']

    pr_unit_mi = pred_unit_mi(input_1, input_2)  # 根据预测里程函数来判断是否需要启用电桩推荐功能

    if distance < 10 * 1000 or start_soc < 5:
        gongkuang_ratio = gongkuang_ratios[0]
    else:
        gongkuang_ratio = gongkuang_ratios[1]
    print('预测的可续航里程_保留_工况：', pr_unit_mi *  (start_soc - remain_soc) * gongkuang_ratio)

    if pr_unit_mi * (start_soc - remain_soc) * gongkuang_ratio > distance:
       aa = {"reach": 1, "ele_stations": []}
       output = json.dumps(aa,  indent=4, ensure_ascii=False)
       return output

    points_pre = gaode_2_points_pre(rn) # rn 高德数据 计算距离，

    # 按规则对点进行稀疏化
    points = sparse_points(points_pre)
    points_pre = sparse_points(points_pre)

    # 基于稀疏化之后的点 进行外扩10公里后的矩形框
    min_lat = points['locationLat'].min() - dist_limit/1000*180/(6371*np.pi)
    max_lat = points['locationLat'].max() + dist_limit/1000*180/(6371*np.pi)
    min_lng = points['locationLng'].min() - dist_limit/1000*180/(6371*np.pi)
    max_lng = points['locationLng'].max() + dist_limit/1000*180/(6371*np.pi)

    # 1.根据矩形框预选充电站
    ## sta ： 所有points的，在max_min_10 框中的 充电站
    sta= station_info.loc[(station_info['station_lat'] >= min_lat) & \
        (station_info['station_lat'] <= max_lat) & \
            (station_info['station_lng'] >= min_lng) & \
                (station_info['station_lng'] <= max_lng)]

    np.random.seed(0)
    sta['space_num'] = np.random.randint(0, 20, sta.shape[0]) # 这里是模拟空位的数量
    sta['electri_city_fee'] = pd.to_numeric(sta['electri_city_fee'], errors='coerce')
    sta['service_fee'] = pd.to_numeric(sta['service_fee'], errors='coerce')
    sta['fee'] = sta['electri_city_fee'] + sta['service_fee']


    # 2.计算所有 电站 与 point 间的距离, 基本条件是距离 <= dist_limit
    points['c=1'] = 1  # points 稀疏化后的点
    sta['c=1'] = 1
    sta_points = sta.merge(points, on='c=1', how='outer')  # 这里是生成了笛卡尔积
    sta_points['distance'] = cal_dist(sta_points[['station_lat', 'station_lng', 'locationLat', 'locationLng']])
    g = sta_points.loc[sta_points['distance'] <= dist_limit]  # 筛选最远10km之内的充电桩
    h = g.sort_values(by=['distance'], ascending=[True])
    # 之前是笛卡尔积的结果，一个电桩与所有的稀疏化点进行了计算，这里先对距离排序后去重，使得每个电站均只归属于一个points
    sta_p = h.drop_duplicates(['station_id'], keep='first')


    # 是否能到达充电站
    sta_p['whether_can_reach'] = 0

    # mask , mask ：替换条件（condition）为True处的值
    # 汽车到稀疏后的高德返回的路径点的累计距离 + 该点到充电桩的（直线）距离 * 模拟的真是距离稀疏(station_ratio)
    sta_p['whether_can_reach'] = sta_p['whether_can_reach'].mask(sta_p['cumsum'] + sta_p['distance'] * station_ratio \
                                                                 <= pr_unit_mi * start_soc * gongkuang_ratio, 1)

    # 车费free策略：车费>距离>空位   fee_stra
    sta_p = sta_p.sort_values(['fee', 'distance', 'space_num'], ascending=[True, True, False], na_position='last')
    sta_p['pre_fee_stra'] = range(sta_p.shape[0])
    sta_p['fee_stra'] =  sta_p.groupby(['serial'])['pre_fee_stra'].rank(method='first', ascending=True)
    sta_p['fee_stra_color'] = 'not_show'
    sta_p['fee_stra_color'] = sta_p['fee_stra_color'].mask((sta_p['fee_stra']>=1) & (sta_p['fee_stra']<=20), 'gray')

    # 空位策略：空位>距离>车费   space_stra
    sta_p = sta_p.sort_values(['space_num', 'fee', 'distance'], ascending=[False, True, True], na_position='last')
    sta_p['pre_space_stra'] = range(sta_p.shape[0])
    sta_p['space_stra'] =  sta_p.groupby(['serial'])['pre_space_stra'].rank(method='first', ascending=True)
    sta_p['space_stra_color'] = 'not_show'
    sta_p['space_stra_color'] = sta_p['space_stra_color'].mask((sta_p['space_stra']>=1) & (sta_p['space_stra']<=20), 'gray')

    sta_p = sta_p.sort_values(['distance', 'fee', 'space_num'], ascending=[True, True, False], na_position='last')
    sta_p['pre_distance_stra'] = range(sta_p.shape[0])

    # distance_stra：距离>车费>空位
    sta_p['distance_stra'] =  sta_p.groupby(['serial'])['pre_distance_stra'].rank(method='first', ascending=True)
    sta_p['distance_stra_color'] = 'not_show'
    sta_p['distance_stra_color'] = sta_p['distance_stra_color'].mask((sta_p['distance_stra']>=1) & \
                                                                     (sta_p['distance_stra']<=20), 'gray')

    # 删除掉3种策略中都是'not_show'的充电站
    cond = ((sta_p['distance_stra_color']=='not_show') & (sta_p['fee_stra_color']=='not_show') & (sta_p['space_stra_color']=='not_show'))
    sta_p = sta_p.loc[~cond]

    ############ 距离策略需要用其他的切分规则，带_pre
    # new_distance_stra
    points_pre['c=1'] = 1
    aaa = points_pre[['locationLat', 'locationLng', 'c=1']].rename(columns={'locationLat': 'LAT', 'locationLng': 'LNG'})
    part = sta_p.loc[~(sta_p['distance_stra_color']=='not_show')]
    part_p = part.merge(aaa, on=['c=1'], how='outer')
    part_p['DIST'] = cal_dist(part_p[['station_lat', 'station_lng', 'LAT', 'LNG']])
    r = part_p.sort_values(by=['DIST'], ascending=[True])
    p_p = r.drop_duplicates(['station_id'], keep='first')
    #
    p_p['new_distance_stra'] = p_p.groupby(['serial'])['DIST'].rank(method='first', ascending=True)

    # 5.red
    # 距离策略
    lis = p_p.loc[p_p['new_distance_stra']==1]['station_id'].to_list()
    cond = (sta_p['station_id'].isin(lis)) & (sta_p['whether_can_reach']==1)
    sta_p['distance_stra_color'] = sta_p['distance_stra_color'].mask(cond, 'red')


    # fee 策略 上一轮筛选中先做初步筛选，筛选出20个数据。这里做第二轮筛选
    u = sta_p.loc[sta_p['distance'] <= dist_limit_of_fee_red]
    v = u.sort_values(['fee_stra'], ascending=True).drop_duplicates(['serial'])
    lis = v['station_id'].to_list()
    cond = (sta_p['station_id'].isin(lis)) & (sta_p['whether_can_reach']==1)
    sta_p['fee_stra_color'] = sta_p['fee_stra_color'].mask(cond, 'red')
    # space 策略
    w = u.sort_values(['space_stra'], ascending=True).drop_duplicates(['serial'])
    lis = w['station_id'].to_list()
    cond = (sta_p['station_id'].isin(lis)) & (sta_p['whether_can_reach']==1)
    sta_p['space_stra_color'] = sta_p['space_stra_color'].mask(cond, 'red')


    r = sta_p[['station_name', 'address', \
               'businehours', 'station_tel', \
               'electri_city_fee', 'service_fee', \
               'space_num', 'station_lng', 'station_lat', \
               'distance_stra_color', 'fee_stra_color', 'space_stra_color']]
    bb = {'reach': 0, 'ele_stations': r.to_dict('records')}
    output = json.dumps(bb, indent=4, ensure_ascii=False)
    output = output.replace(': NaN',':null')
    return output


if __name__ == '__main__':

    input_1 = open(r'输入参数1.json', encoding='utf-8-sig').read() # 对新能源汽车剩余续航里程的预测
    # 高德 input_2
    gaodekey = '' # 账号
    api = ''
    start_point = '121.320081,31.193964' #上海虹桥站
    end_point = '116.407387,39.904179'#北京市
    # start_point = '121.621796,31.256262' #东方万国企业中心
    # end_point = '121.153687,31.161311' #青浦宝龙广场
    url = api + 'key=' + gaodekey + '&origin=' + start_point + '&destination=' + end_point

    input_2 = json.dumps(requests.get(url).json(), indent=4, ensure_ascii=False)
    output = refer_ele_sta(input_1, input_2)




